#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>

#include <common/macro/build.h>
#include <common/macro/array.h>
#include <common/macro/debug.h>
#include <common/charreader.h>
#include <common/io.h>
#include <common/util.h>
#include <table/table.h>

API_PUBLIC table_t *table_new(char *name) {
	table_t *table;

	table = malloc(sizeof(*table));

	table->name = strdup(name);
	table->columns = NULL;
	table->columns_c = 0;

	return table;
}

API_PUBLIC void table_destroy(table_t *table) {
	int i;

	if (table == NULL) { return; }

	for (i = 0; i < table->columns_c; i++) {
		column_destroy(table->columns[i]);
	}
	free(table->columns);
	free(table->name);
	free(table);
}

API_PUBLIC void table_add_column(table_t *table, column_t *column) {
	assert(table != NULL);
	assert(column != NULL);

	ARRAY_APPEND(table->columns, table->columns_c, column);
}

API_PUBLIC int table_get_column_id(table_t *table, char *name) {
	int i;

	for (i = 0; i < table->columns_c; i++) {
		if (strcmp(table->columns[i]->name, name) == 0)
			return i;
	}

	return -1;
}

API_PUBLIC int table_get_column_count(table_t *table) {
	return table->columns_c;
}

API_PUBLIC char *table_get_column_name(table_t *table, int column_id) {
	if (column_id >= table->columns_c)
		return NULL;
	return table->columns[column_id]->name;
}

API_PUBLIC char *table_get_column_datatype(table_t *table, int column_id) {
	if (column_id >= table->columns_c)
		return NULL;
	return table->columns[column_id]->datatype->name;
}

API_PUBLIC int table_get_column_datatype_args(table_t *table, int column_id, char ***argv, int *argc) {
	if (column_id >= table->columns_c)
		return -1;
	
	*argv = table->columns[column_id]->dt_args_raw;
	*argc = table->columns[column_id]->dt_args_raw_c;
	return 0;
}

API_LIBLOCAL int table_serialize_file(table_t *table, iohandle_t out) {
	int rv;
	uint16_t i, items;

	assert(table != NULL);
	assert(out != NULL);

	/* Column name. */
	if (io_write(table->name, strlen(table->name) + 1, 1, out) != 1) {
		return -1;
	}

	/* All columns: */
	/*   - column count */
	items = (uint16_t)table->columns_c;
	if (io_write(&items, sizeof(items), 1, out) != 1) {
		return -1;
	}

	/*   - column themselves */
	for (i = 0; i < items; i++) {
		rv = column_serialize_file(table->columns[i], out);
		if (rv != 0)
			return rv;
	}

	return 0;
}

API_LIBLOCAL table_t *table_deserialize_file(iohandle_t in) {
	int c;
	charreader_t *charreader;
	column_t *column;
	table_t *table;
	uint16_t i, items;

	assert(in != NULL);

	charreader = charreader_new();
	
	/* Table name. */
	do {
		c = fgetc(in);
		if (c < 0) {
			charreader_destroy(charreader);
			return NULL;
		}
		charreader_pushchar(charreader, c);
	} while (c != '\0');
	table = table_new(charreader_getstring(charreader));
	charreader_destroy(charreader);

	/* All columns: */
	/*   - column count */
	if (io_read(&items, sizeof(items), 1, in) != 1) {
		table_destroy(table);
		return NULL;
	}

	/*   - column themselves */
	for (i = 0; i < items; i++) {
		column = column_deserialize_file(in);
		if (column == NULL) {
			table_destroy(table);
			return NULL;
		}
		table_add_column(table, column);
	}

	return table;
}

API_LIBLOCAL ssize_t table_serialize(table_t *table, char *out, ssize_t out_size) {
	ssize_t offset, itemlen;
	uint16_t i, items;

	assert(table != NULL);
	assert(out != NULL);

	offset = 0;

	/* Table name. */
	itemlen = strlen(table->name) + 1;
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], table->name, itemlen);
	offset += itemlen;

	/* All columns: */
	/*   - columns count */
	items = (uint16_t)table->columns_c;
	itemlen = sizeof(items);
	if (itemlen > out_size - offset) { return -1; }
	memcpy(&out[offset], &table->columns_c, itemlen);
	offset += itemlen;

	/*   - actual columns */
	for (i = 0; i < items; i++) {
		itemlen = column_serialize(table->columns[i], &out[offset], out_size - offset);
		if (itemlen == -1) {
			return -1;
		}
		offset += itemlen;
	}

	return offset;
}

API_LIBLOCAL ssize_t table_deserialize(table_t **table, const char *in, ssize_t in_size) {
	char *table_name;
	column_t *column;
	ssize_t offset, itemlen;
	uint16_t i, items;

	assert(table != NULL);
	assert(in != NULL);

	offset = 0;

	/* Column name. */
	itemlen = util_strnlen(&in[offset],  in_size);
	if (itemlen > in_size - offset) { return -1; }
	table_name = malloc(itemlen + 1);
	memcpy(table_name, &in[offset], itemlen + 1);
	table_name[itemlen] = '\0';
	offset += itemlen + 1;

	*table = table_new(table_name);
	free(table_name);

	/* All columns: */
	/*   - columns count */
	itemlen = sizeof(items);
	if (itemlen > in_size - offset) { column_destroy(column); return -1; }
	memcpy(&items, &in[offset], itemlen);
	offset += itemlen;

	/*   - actual columns */
	for (i = 0; i < items; i++) {
		itemlen = column_deserialize(&column, &in[offset], in_size - offset);
		if (itemlen == -1) {
			return -1;
		}
		offset += itemlen;

		table_add_column(*table, column);
	}

	return 0;
}
